iT邦幫忙

2022 iThome 鐵人賽

DAY 6
2

OpenID Connect 所定義的術語中,有兩個互相傳遞資訊的角色為 OpenID 服務提供者(簡稱 OP)與依賴方(簡稱 RP)。

Hydra 的設計是把控制 OpenID Connect 流程的任務交由 OpenID Provider 也就是 Hydra 來處理,而登入驗證實作交給 Login Provider 處理,授權實作交給 Consent Provider 處理。依照這個設計,若想要用 Hydra 模擬完整的 OP 與 RP,就需要啟動四個以上的服務才能正常運行。聽起來很複雜,這件事留到未來再慢慢釐清,今天將會使用 Hydra 提供的 Docker Compose 的設定檔以及 Docker Image 來準備模擬環境。

準備 Docker 環境可以參考 30 天與鯨魚先生做好朋友 Day 2 建置 Docker 環境。筆者是使用 Lima 搭配預設的 containerd 建置的,訊息可能會跟 Docker 略有不同,但原則上差異不大。

五分鐘快速開始

官方有提供五分鐘快速體驗教學,以下的內容是參考官方教學改寫的。

首先要把 Hydra 原始碼下載回來,並切換成 v1.11.10 版本:

git clone https://github.com/ory/hydra.git
cd hydra && git checkout v1.11.10

原始碼的根目錄有很多 quickstart 檔,這些都是啟動懶人包服務的 Docker Compose 配置設定檔:

❯ ls quickstart*
quickstart-cockroach.yml
quickstart-cors.yml
quickstart-debug.yml
quickstart-hsm.yml
quickstart-jwt.yml
quickstart-mysql.yml
quickstart-postgres.yml
quickstart-prometheus-config.yml =>> 這個是 Prometheus 設定檔先不管它
quickstart-prometheus.yml
quickstart-tracing.yml
quickstart.yml

Hydra 快速體驗的設計方法是,以 quickstart.yml 為基底,再依需求追加額外功能。比方說如果想要 MySQL 的版本,官方提供的指令如下:

docker-compose -f quickstart.yml \
    -f quickstart-mysql.yml \
    up --build

以這個例子來說明,Docker Compose 會把 quickstart.yml 當作基礎,然後 quickstart-mysql.yml 設定追加或覆蓋上去。這個例子實際的意思指的是使用 MySQL 作為儲存服務,如果想用 PostgreSQL 的話,則是改用 quickstart-postgres.yml

containerd 實際啟動 MySQL 或 migratie 可能會有時間差問題,導致 Container 無法正常啟動,下面是單一服務逐步啟動的方法:

# MySQL 啟動約要 20 秒時間
❯ docker-compose -f quickstart.yml -f quickstart-mysql.yml up -d mysqld

# Hydra 資料庫 migrate 大概需要 20 秒
❯ docker-compose -f quickstart.yml -f quickstart-mysql.yml up -d hydra-migrate

# 啟動 Hydra
❯ docker-compose -f quickstart.yml -f quickstart-mysql.yml up -d hydra

# 啟動測試用的 Login 與 Consent 服務
❯ docker-compose -f quickstart.yml -f quickstart-mysql.yml up -d consent

啟動完後,可以使用 docker-compose ps 確認開啟了哪些 server:

$ docker-compose -f quickstart.yml -f quickstart-mysql.yml ps
NAME                     COMMAND                   SERVICE          STATUS                      PORTS
hydra_hydra-migrate_1    "hydra migrate -c /e…"    hydra-migrate    Exited (0) 3 minutes ago
hydra_mysqld_1           "docker-entrypoint.s…"    mysqld           running                     0.0.0.0:3306->3306/tcp
hydra_consent_1          "/bin/sh -c npm run …"    consent          running                     0.0.0.0:3000->3000/tcp
hydra_hydra_1            "hydra serve -c /etc…"    hydra            running                     0.0.0.0:4444->4444/tcp, 0.0.0.0:4445->4445/tcp, 0.0.0.0:5555->5555/tcp

以下使用表格整理出已啟動服務和用途:

Service Port 用途
hydra-migrate 執行 DB migration,當成功執行完後就會關閉
mysqld 3306 資料庫服務
consent 3000 Hydra 官方提供身分驗證與授權的測試服務
hydra 4444、4445、5555 Hydra 的主要服務

啟動完成後可以使用 curl 指令來確定 Hydra 是否正常啟用:

curl http://127.0.0.1:4444/health/ready
curl http://127.0.0.1:4445/health/ready

其中 4444 是提供給公開使用者或應用程式存取的, 4445 則是內部管理員使用的。官方的 Swagger 文件在將 API 分組時,是把 4444 稱為 Public API, 4445 則是 Admin API,未來會用這兩個術語來稱呼這兩個 port 所開放的 API。

兩個 port 有各自的 health check API,若一切正常則會回應:

{"status":"ok"}

註冊 RP

剛建立好服務,資料庫會是空的,因此需要手動註冊一個 RP 才能開始執行登入流程。

可以透過 Admin API 或是透過 Hydra CLI 註冊:

docker-compose -f quickstart.yml \
    -f quickstart-mysql.yml \
    exec hydra hydra --endpoint http://127.0.0.1:4445/ clients --skip-tls-verify \
        create \
              --id my-rp \
              --secret my-secret \
              --grant-types authorization_code,implicit,client_credentials,refresh_token \
              --response-types "code,token,id_token,token code,code id_token,id_token token,id_token token code" \
              --scope openid \
              --token-endpoint-auth-method client_secret_basic \
              --callbacks https://oidcdebugger.com/debug

這個指令會使用 container 執行 Hydra CLI,去呼叫 Admin API 並帶入對應的參數來註冊 client。裡面可以看到 Day02 - Day04 所提到的許多關鍵字,以下一一說明。

首先是 id 與 secret,id 指的是 RP 的唯一識別碼,換句話說就是指應用程式的「帳號」,而 secret 指的是這個帳號的「密碼」。

--id my-rp --secret my-secret

Hydra 是允許可以直接在註冊的時候直接輸入密碼,只是它就會提醒你這樣會有被別人偷看 history 的風險。

You should not provide secrets using command line flags, the secret might leak to bash history and similar systems

當然也可以選擇不輸入 secret,Hydra 會透過亂數產生 secret 並印出:

OAuth 2.0 Client ID: my-rp
OAuth 2.0 Client Secret: ECDb639*******************

接著是 Grant Types,指應用程式能夠使用哪些授權類型。這裡是啟用所有類型的範例:

--grant-types authorization_code,implicit,client_credentials,refresh_token

Response Types,指應用程式能夠使用哪些回傳類型。這裡是啟用所有類型,但注意的是,多個類型回傳方法也算是「一種」類型:

--response-types "code,token,id_token,token code,code id_token,id_token token,id_token token code"

Scope 是應用程式能夠請求的授權範圍,這裡對應到了 OpenID Connect 協定提到的 openid

--scope openid

Token Endpoint Auth Method 指的是要取得 Token 的時候要用什麼方法驗證「應用程式的身分」,這裡是使用 Basic Auth:

--token-endpoint-auth-method client_secret_basic

Callback 指的是授權完成後,要回到哪個指定的路徑:

--callbacks https://oidcdebugger.com/debug

OpenID Connect debugger 是一個測試用的網址,可以針對 OpenID Connect 的流程做測試,剛好適合用在今天的快速體驗。

啟動登入流程

一切就緒之後,再來就可以開始走登入流程了,首先打開測試網址,輸入以下資訊:

6-1.png

欄位內容如下:

欄位 內容
Authorize URI (required) http://127.0.0.1:4444/oauth2/auth
Redirect URI (required) https://oidcdebugger.com/debug
Client ID (required) my-rp
Scope (required) openid
State (程式自動產生)
Nonce (程式自動產生)
Response type (required) 先勾 tokenid_token 這兩個
Response mode (required) fragment

PKCE 是另一個主題,未來會再另外說明。

填完上面資訊後,網站最下面會根據 OpenID Connect 協定產生授權請求如下:

http://127.0.0.1:4444/oauth2/auth
  ?client_id=my-rp
  &redirect_uri=https://oidcdebugger.com/debug
  &scope=openid
  &response_type=id_token token code
  &response_mode=fragment
  &state={程式自動產生}
  &nonce={程式自動產生}

這裡有用七個參數,是 OpenID Connect 定義的 Authentication Request 規格,以下列出欄位和說明:

欄位 必填 說明
client_id REQUIRED RP 的唯一識別碼,是註冊的時候帶入的 ID。
redirect_uri REQUIRED 當授權完成後,會將使用者導去哪,這裡的值必須與註冊的 callback 完全一致。
scope REQUIRED(OpenID Connect) 授權範圍,使用 openid 表示要發身分識別 token(ID token)。
response_type REQUIRED 回傳給 RP 的回應類型。
response_mode OPTIONAL 回應模式,指定回傳 RP 的方法。
state RECOMMENDED 此為防止 CSRF 攻擊的隨機字串。
nonce OPTIONAL 為了避免重送攻擊的一次性亂數字串。

按下去,啟動登入吧!

按下 SEND REQUEST 後,網址就會轉到 http://127.0.0.1:3000/login,也就是登入頁。這是 Hydra 用來做測試的:

6-2.png

上面有提示測試的使用者帳密為何,輸入 foo@bar.comfoobar 並點擊 Log in 之後,會出現第二個頁面是授權頁:

6-3.png

上面的 openid 核選鈕打勾後即代表授權此項目,接著點擊 Allow access 後,接著 Hydra 會帶著授權回應回到 Debugger。這裡可以先關心一下網址,它其實是使用 Fragment 來傳輸授權回應的:

https://oidcdebugger.com/debug#access_token=br1X-KqIP7taR_SJ4MVH-y_5EEIOFUW49vFkAaVtVIM.soHhJsvW4vMdU-XGZGejKQ_YyLvUQ6ISFDRDAicjbBQ&expires_in=3599&id_token=eyJhbGciOiJSUzI1NiIsImtpZCI6InB1YmxpYzpoeWRyYS5vcGVuaWQuaWQtdG9rZW4iLCJ0eXAiOiJKV1QifQ.eyJhY3IiOiIwIiwiYXRfaGFzaCI6IjRZTG85WnQ2a3FfZ2hHMy1tTWtJUWciLCJhdWQiOlsibXktcnAiXSwiYXV0aF90aW1lIjoxNjYzNjg3ODA4LCJleHAiOjE2NjM2OTE0MTEsImlhdCI6MTY2MzY4NzgxMSwiaXNzIjoiaHR0cDovLzEyNy4wLjAuMTo0NDQ0LyIsImp0aSI6ImY0NDBmMmQzLTk1ZWUtNGNlYS04ZmNiLTBhZDc0NDQzODVkMyIsIm5vbmNlIjoiaDB1djBxMDd3eSIsInJhdCI6MTY2MzY4Nzc5OSwic2lkIjoiMDUzYTczYWQtZWE4My00YTQ3LWI1ZjYtNWJkMjc3MjhiYjgyIiwic3ViIjoiZm9vQGJhci5jb20ifQ.XsUk-BTb3IIGqW8khtFRFVGLGlwmCa_wRdyydz6sHB8w6FddfAVHoAuzNRwjEYK5tA0Z-bcqJdxEwlukpUpNf5KnjLBl180nZu0Q0YvRHS_kAyvL3mtnFb4XuFL1BdaIWToa_ht3XCWc64XlSymGXVUb-Ns5yqJK63i8RyUP4hauf3P79jl7NrFExhqXAmbeFW115omT_JNL_s0yTq-X2w84elCdYb-zEtCrRWdjWMrfpmoEQRwr3OxBHWEDD03TggW3Nh_vMlqqsN8PANU8GQlbpngHHtmbkGZptAWog07RxN2ofO8-t9X63_bfqyu9anWRJ0XO4-1UVTxtXuh19XTxg85CyWAk5ymucKHwS_7996S1NUvZwrADXG-Z3WbiKGGikQcgU0WXKwN-eBTk5-OV8ms_OueUGT7hccBn4nA1lNXuJ3C5PchqZQeEg7xO8Zg1CsEawbFJwXyzDbXJcBCxsD4HVrSgsVZ2pBnZ2teJhFTMenbJ-R2iNZMozYPP8nhXx0ACtqlLFq98ukcsyWiYrGvCMI3XH7IByr1YxqrqT_nI5zuJHljqY4JE-vIIKxoGObUPfUtoPr0ZHHJFxt97PVnQ9xphU4VPljknxc-nrdl6N0Xb7dM55qVSbOA8r6GDMjebveHLJJoOntjZr4vT0GwSfyYuY7J3yHuPhUU&scope=openid&state=x5bqkzhxurp&token_type=bearer

授權回應資料解析結果如下:

6-4.png

回到 Debugger 後,Debugger 會把授權回應裡面的內容解析出來,其中有兩個資訊,是 Access tokenID token,這兩個剛好對應到回應類型(Response type)的 tokenid_token

Access token 需要呼叫 API 來確認,這個之後的章節再來確認。而 ID token 的內容可以立即解析出來,如下:

Header

{
   "alg": "RS256",
   "kid": "public:hydra.openid.id-token",
   "typ": "JWT"
}

Payload

{
   "acr": "0",
   "at_hash": "4YLo9Zt6kq_ghG3-mMkIQg",
   "aud": [
      "my-rp"
   ],
   "auth_time": 1663687808,
   "exp": 1663691411,
   "iat": 1663687811,
   "iss": "http://127.0.0.1:4444/",
   "jti": "f440f2d3-95ee-4cea-8fcb-0ad7444385d3",
   "nonce": "h0uv0q07wy",
   "rat": 1663687799,
   "sid": "053a73ad-ea83-4a47-b5f6-5bd27728bb82",
   "sub": "foo@bar.com"
}

裡面幾個欄位代表的意義可以參考 OpenID Connect 說明

欄位 說明
at_hash 這是從 Access token 做雜湊演算後的結果,是用來確認 ID token 與 Access token 是否為同時發行的
aud ID token 的受眾,簡單來說就是指 ID token 要給誰用
auth_time 使用者身分驗證發生的時間
exp 過期時間
iat 發行時間
iss 發行單位
jti JWT 的唯一識別碼
sid Session ID,用在登出協定
sub 使用者的唯一識別碼

到這邊為止,Access token 與 ID token 應用程式都拿到了,因此授權的流程到此就完成了。再來應用程式把 ID token 保存起來,直到執行登出或過期;而 Access token 則是存取資源所要使用的,未來的章節再來討論。


上一篇
Hydra 如何做到輕鬆導入
下一篇
實作登入系統
系列文
30 天與九頭蛇先生做好朋友23
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言